-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue #12104 - HTTP/1.0 should produce a valid Connection
header, never blank/null.
#12105
Conversation
* make it easier to test different scenarios
+ Fix empty `Connection:` response header issue seen on HTTP/1.0
header issue seen on HTTP/1.0
Setting this to draft mode, as there's still a problem with the response headers on HTTP/1.0 to address. |
header issue seen on HTTP/1.0 when Servlet uses `Connection: keep-alive` against a request that wasn't persistent.
…9-errorhandlertest-restore
@gregw this started out being a fix for HTTP/1.0 responses sometimes having an empty |
boolean hasValue = StringUtil.isNotBlank(field.getValue()); | ||
boolean hasKeepAlive = field.contains(HttpHeaderValue.KEEP_ALIVE.asString()); | ||
boolean hasClose = field.contains(HttpHeaderValue.CLOSE.asString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whilst this style is easily readable, it contains a lot of redundant code/tests. If hasValue==false
, then you can break straight away and avoid subsequent work.
If it hasKeepAlive
is true, then in your code there is no need to lookup hasClose
(but see below).
} | ||
if (field.contains(HttpHeaderValue.CLOSE.asString())) | ||
else if (hasClose) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we want the else here. If it came Connection: keep-alive, close
then either that is a 400 or at least we should close. So perhaps invert the order of the tests and look for close, else look for keepAlive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was reading the Hop-by-hop parts of the spec in light of other tests with have for this.
Looks like if we receive a comma delimited list, then it's a 400 Bad Request in normal operations.
BUT if we receive that when we are a proxy, then we have to act on all of the various Hop-by-hop headers (of which Connection
is just one of them).
BUT we can create a response with multi-value Connection
at all times (normal operation, and proxy operation), and we are only on the hook to act on a persistence state change for our hop.
I'm at a loss on the exact meaning of "our hop" in these cases.
Eg: If the request was keep-alive
and response from the servlet is close, keep-alive
vs keep-alive, close
, what do we do at the prepareRequest / httpGenerator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to 400 for both a Keep-Alive and Close. The close is a must-not persist and the Keep-Alive is a may persist, so they combine easily to a must-not persist.
boolean hasConnectionClose = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); | ||
boolean hasConnectionKeepAlive = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, don't lookup values if we don't need them, especially in this case as it involves a full iteration to do each lookup.
So lookup only if needed and perhaps look for close first, so it can override keepalive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that each of these could then be looked up multiple times instead.
So I took the case of the lookup occurring 4 times down to 2, which I felt was better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A single iteration through is best, I'll work on that after you have had a look at the ResponseHeadersTest
.
In particular the new tests ...
testHTTP10ConnectionBehavior
for when the client is using HTTP/1.0testHTTP11ConnectionBehavior
for when the client is using HTTP/1.1
Are you good with the set of expectations for those?
Each test case arguments have 3 parameters.
- The
Connection
request header as sent from the Client (null means no Connection header sent) - The
Connection
response header as set by an application (Servlet in this case. null means no Connection header is set) - The expected
Connection
response header as seen by the client. (null means no Connection header should be present received)
Once I get that set of tests sane, then I can look closer at the other tests that are now failing due to this PR.
Some of which are suspect (like a test client that uses HTTP/1.0 but also Connection: close
, which is not typical, but we should accept it, and the new testcases I mentioned has variants of this scenario too)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you take just the changes to the ResponseHeadersTest
back to jetty-12.0.x
HEAD and run them, you'll quickly see the various cases that currently return a Connection:
(empty value) response header.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not saying avoid local variables with those looked up values (so we don't need to lookup multiple times), but I am saying delay doing the lookup until it is known that it is needed.
String resultingValue = Stream.of(field.getValues()) | ||
.filter(s -> !HttpHeaderValue.KEEP_ALIVE.is(s)) | ||
.collect(Collectors.joining(", ")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if string operations here would be better than streams...
Also perhaps we could do this in the condition bodies above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'm not a fan of the stream operation here, but it was here before I started this, so I left it.
I was going to look into adding a HttpField.removeValue(String)
method as a possible replacement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, so that. I agree a removeValue method would allow us to hide the details.
@gregw the branch Build results -> https://jenkins.webtide.net/blue/organizations/jenkins/jetty.project/activity?branch=jetty-12.0.x-null-connection-response-header Basically, this is the state of testHTTP10ConnectionBehavior(String, String, String)
testHTTP11ConnectionBehavior(String, String, String)
|
@joakime I definitely agree that sending a blank connection is wrong, but I think I see a pretty simple fix for that. Let me have a play with your branch with just the test changes and see if I can come up with a minimal fix... stand by... |
Actually, I'm not sure why you have done this as eeX tests. I think this is a core issue??? I may move your test to core.... |
It was originally discovered while testing ee8 issues reported from the community. But now that I've been deep in it, I don't see this being a EE8/EE9/EE10 specific issue. |
OH! And when I enabled the disabled test |
See my fix on #12127. I moved your test to core and combined. I changed a few expected values. The fix is in the |
Closing in favor of alternate approach in #12127 |
Connection:
response header issue seen on HTTP/1.0Fixes #12104